home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Skunkware 98
/
Skunkware 98.iso
/
src
/
net
/
bind-contrib.tar.gz
/
bind-contrib.tar
/
contrib
/
msql
/
dnswalk
< prev
next >
Wrap
Text File
|
1996-10-25
|
14KB
|
472 lines
#!/usr/local/bin/perl
# dnswalk Walk through a DNS tree, pulling out zone data and
# dumping it in a directory tree
#
# $Id: dnswalk,v 8.1 1996/10/25 04:57:41 vixie Exp $
#
# check data collected for legality using standard resolver
#
# invoke as dnswalk domain > logfile
# Options:
# -r Recursively descend subdomains of domain
# -f Force a zone transfer, ignore existing axfr file
# -i Suppress check for invalid characters in a domain name.
# -a turn on warning of duplicate A records.
# -d Debugging
# -m Check only if the domain has been modified. (Useful only if
# dnswalk has been run previously.)
# -F Enable "facist" checking. (See man page)
# -l Check lame delegations
# -D dir Use 'dir' as base directory for saved axfr files
require "getopts.pl";
do Getopts("D:rfiadmFl");
# Where all zone transfer information is saved. You can change this to
# something like /tmp/dnswalk if you don't want to clutter up the current
# directory
if ($opt_D) {
$basedir = $opt_D;
} else {
$basedir = ".";
}
($domain = $ARGV[0]) =~ tr/A-Z/a-z/;
if ($domain !~ /\.$/) {
die "Usage: dnswalk domain\ndomain MUST end with a '.'\n";
}
if (! -d $basedir) {
mkdir($basedir,0777) || die "Cannot create $basedir: $!\n";
}
&dowalk($domain);
exit;
sub dowalk {
local (@subdoms);
local (@sortdoms);
local ($domain)=$_[0];
$modified=0;
# ($file,@subdoms)=&doaxfr($domain); /* perl bug */
@subdoms=&doaxfr($domain);
$file=shift(@subdoms);
if ($file && !($opt_m && !$modified)) {
&checkfile($file,$domain);
}
else {
print STDERR "skipping...\n";
}
@sortdoms = sort byhostname @subdoms;
local ($subdom);
if ($opt_r) {
foreach $subdom (@sortdoms) {
&dowalk($subdom);
}
}
}
# try to get a zone transfer, trying each listed authoritative server if
# if fails.
sub doaxfr {
local ($domain)=@_[0];
local (%subdoms)=();
local ($subdom);
local ($serial);
local ($foundsoa)=0; # attempt to make up for dig's poor
# error handling
($path=&host2path($domain)) =~ tr/A-Z/a-z/;
local(@servers) = &getauthservers($domain);
&printerr("warning: $domain has only one authoritative nameserver\n") if (scalar(@servers) == 1);
&printerr("warning: $domain has NO authoritative nameservers!\n") if (scalar(@servers) == 0);
if ((-f "$basedir/$path/axfr") && (!$main'opt_f)) {
open(DIG,"<$basedir/$path/axfr") || die "cannot open $basedir/$path/axfr: $!\n";
while (<DIG>) {
chop;
if (/(\d+)\s*; ?serial/) {
$serial=$1;
}
if (/(\S+)\s*\d+\s+NS/) {
$subdom = $1;
$subdom =~ tr/A-Z/a-z/;
if ((!&equal($subdom,$domain)) && ( !$subdoms{$subdom})) {
$subdoms{$subdom}=1;
}
}
}
# if there's no serial number in file, assume it is corrupt
if ($serial) {
foreach $server (@servers) {
$authserno=&getserno($domain,$server);
last if ($authserno);
}
if ($authserno <= $serial) {
print STDERR "Using existing zone transfer info for $domain\n";
return ("$basedir/$path/axfr", keys %subdoms);
}
}
}
&mkdirpath($path);
SERVER:
foreach $server (@servers) {
$foundsoa=0;
$SIG{'INT'}="nop;";
print STDERR "Getting zone transfer of $domain from $server...";
open(DIG,"dig axfr $domain \@$server 2>/dev/null |");
open(DIGOUT,">$basedir/$path/axfr") || die "cannot open $basedir/$path/axfr: $!\n";
@subdoms=undef;
while (<DIG>) {
if (/(\S+)\s*\d+\s+NS/) {
$subdom = $1;
$subdom =~ tr/A-Z/a-z/;
if ((!&equal($subdom,$domain)) && ( !$subdoms{$subdom})) {
$subdoms{$subdom}=1;
}
}
elsif (/\S+\s*\d+\s+SOA/) {
$foundsoa=1;
}
print DIGOUT $_;
}
if ($? || !$foundsoa) {
print STDERR "failed.\n";
close(DIGOUT);
next SERVER;
}
print STDERR "done.\n";
close(DIGOUT);
close(DIG);
last SERVER;
} # foreach #
$SIG{'INT'}=undef;
if ($? || !$foundsoa) {
print STDERR "All zone transfer attempts of $domain failed\n";
print "Cannot check $domain: no available nameservers!\n";
unlink("$basedir/$path/axfr");
&rmdirpath($path);
return undef;
}
$modified=1;
return ("$basedir/$path/axfr", keys %subdoms);
}
# returns "edu/psu/pop" given "pop.psu.edu"
sub host2path {
join('/',reverse(split(/\./,$_[0])));
}
# makes sure all directories exist in "foo/bar/baz"
sub mkdirpath {
local (@path)=split(/\//,$_[0]);
local ($dir)=$basedir;
foreach $p (@path) {
$dir .= "/".$p;
if (! -d $dir) {
mkdir($dir, 0777) || die "Cannot mkdir $dir: $!\n";
}
}
}
# remove empty directories in path
sub rmdirpath {
local (@path)=split(/\//,$_[0]);
local (@dirs);
local ($dir)=$basedir;
foreach $p (@path) {
push(@dirs, ($dir .= "/".$p));
}
foreach $p (reverse(@dirs)) {
last if !rmdir($p);
}
}
sub getserno {
local ($serno)="";
$SIG{'INT'}="nop;";
open(DIG,"dig soa $_[0] \@$_[1] 2>/dev/null|");
while (<DIG>) {
if (/(\d+)\s*; ?serial/) {
$serno=$1;
}
}
close(DIG);
$SIG{'INT'}=undef;
return $serno;
}
sub getauthservers {
local ($domain)=$_[0];
local ($master)=&getmaster($domain);
local ($foundmaster)=0;
local ($s);
open(DIG,"dig +noau ns $_[0] 2>/dev/null|");
local(@servers)=();
local(%servhash)=();
while (<DIG>) {
chop;
tr/A-Z/a-z/;
if (/\S+\s+\d+\s+ns\s+(\S+)/) {
$s=$1;
if (&equal($s,$master)) {
$foundmaster=1; # make sure the master is at the top
} else {
push(@servers,$s) if ($servhash{$s}++<1);
}
}
}
close(DIG);
if ($foundmaster) {
unshift(@servers,$master);
}
return @servers;
}
sub getmaster { # return 'master' server for zone
local ($master)="";
$SIG{'INT'}="nop;";
open(DIG,"dig soa $_[0] 2>/dev/null|");
while (<DIG>) {
tr/A-Z/a-z/;
if (/.*\t\d+\tsoa\t(\S+) /) {
$master=$1;
}
}
close(DIG);
$SIG{'INT'}=undef;
return $master;
}
# open result of zone tranfer and check lots of nasty things
# here's where the fun begins
sub checkfile {
open(FILE,"<$_[0]") || die "Cannot open $_[0]: $!\n";
undef $errlist;
print "Checking $domain\n";
local (%glues)=(); # look for duplicate glue (A) records
local ($name, $aliases, $addrtype, $length, @addrs);
local ($prio,$mx);
local ($soa,$contact);
local ($lastns); # last NS record we saw
local (@keys); # temp variable
$soa=undef;
$doubledom = $domain . $domain;
$doubledom =~ s/(\W)/\\\1/g; # quote string so it's a regexp
while (<FILE>) {
chop;
if (/^;/) {
if (/(.*[Ee][Rr][Rr][Oo][Rr].*)/) {
# print any dig errors
print $1 ."\n";
next;
}
}
next if /^$/; # skip blanks
# check to see if there is a "foo.bar.baz.bar.baz."
# probably a trailing-dot death.
if (/$doubledom/) {
&printerr(" $_: domain occurred twice, forgot trailing '.'?\n");
}
split(/\t/);
# 0=key 2=rrtype 3=value (4=value if 2=MX)
next if ($_[0] =~ /;/);
# complain only for mail names
if (($_[0] =~ /[^\*][^-A-Za-z0-9.]/) && (!$opt_i) && (($_[2] eq "A") || ($_[2] eq "MX"))) {
&printerr(" $_[0]: invalid character(s) in name\n");
}
if ($_[2] eq "SOA") {
print STDERR 's' if $opt_d;
if (! $soa) { # avoid duplicate SOA's. Argh.
($soa,$contact) = $_[3] =~ /(\S+)\s+(\S+)/;
print "SOA=$soa contact=$contact\n";
}
} elsif ($_[2] eq "PTR") {
print STDERR 'p' if $opt_d;
if (scalar((@keys=split(/\./,$_[0]))) == 6 ) {
# check if forward name exists, but only if reverse is
# a full IP addr
# skip ".0" networks
if ($keys[0] ne "0") {
if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($_[3])) && !$?) {
&printerr(" gethostbyname($_[3]): $!\n");
}
else {
if (!$name) {
&printerr(" $_[0] PTR $_[3]: unknown host\n");
}
elsif (!&equal($name,$_[3])) {
&printerr(" $_[0] PTR $_[3]: CNAME (to $name)\n");
}
elsif (!&matchaddrlist($_[0])) {
&printerr(" $_[0] PTR $_[3]: A record not found\n");
}
}
}
}
} elsif (($_[2] eq "A") ) {
print STDERR 'a' if $opt_d;
# check to see that a reverse PTR record exists
if (!(($name,$aliases,$addrtype,$length,@addrs)=gethostbyaddr(pack('C4', split(/\./,$_[3])),2)) && !$?) {
&printerr(" gethostbyaddr($_[3]): $!\n");
}
else {
if (!$name) {
# hack - allow RFC 1101 netmasks encoding
if ( $_[3] !=~ /^255/) {
&printerr(" $_[0] A $_[3]: no PTR record\n");
}
}
elsif ($opt_F && !&equal($name,$_[0])) {
&printerr(" $_[0] A $_[3]: points to $name\n") if ((split(/\./,$name))[0] ne "localhost");
}
if ($main'opt_a) {
# keep list in %glues, report any duplicates
if ($glues{$_[3]} eq "") {
$glues{$_[3]}=$_[0];
}
elsif (($glues{$_[3]} eq $_[0]) && (!&equal($lastns,$domain))) {
&printerr(" $_[0]: possible duplicate A record (glue of $lastns?)\n");
}
}
}
} elsif ($_[2] eq "NS") {
$lastns=$_[0];
print STDERR 'n' if $opt_d;
# check to see if object of NS is real
&checklamer($_[0],$_[3]) if ($main'opt_l);
if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($_[3])) && !$?) {
&printerr(" gethostbyname($_[3]): $!\n");
}
else {
if (!$name) {
&printerr(" $_[0] NS $_[3]: unknown host\n");
} elsif (!&equal($name,$_[3])) {
&printerr(" $_[0] NS $_[3]: CNAME (to $name)\n");
}
}
} elsif ($_[2] eq "MX") {
print STDERR 'm' if $opt_d;
# check to see if object of mx is real
($prio,$mx)=split(/ /,$_[3]);
if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($mx)) && !$?) {
&printerr(" gethostbyname($mx): $!\n");
}
else {
if (!$name) {
&printerr(" $_[0] MX $_[3]: unknown host\n");
}
elsif (!&equal($name,$mx)) {
&printerr(" $_[0] MX $_[3]: CNAME (to $name)\n");
}
}
} elsif ($_[2] eq "CNAME") {
print STDERR 'c' if $opt_d;
if (!(($name, $aliases, $addrtype, $length, @addrs)=gethostbyname($_[3])) && !$?) {
&printerr(" gethostbyname($_[3]): $!\n");
}
else {
if (!$name) {
&printerr(" $_[0] CNAME $_[3]: unknown host\n");
} elsif (!&equal($name,$_[3])) {
&printerr(" $_[0] CNAME $_[3]: CNAME (to $name)\n");
}
}
}
}
print STDERR "\n" if $opt_d;
close(FILE);
}
# prints an error message, suppressing duplicates
sub printerr {
local ($err)=$_[0];
if ($errlist{$err}==undef) {
print $err;
print STDERR "!" if $opt_d;
$errlist{$err}=1;
} else {
print STDERR "." if $opt_d;
}
}
sub equal {
# Do case-insensitive string comparisons
local ($one)= $_[0];
local ($two)= $_[1];
$stripone=$one;
if (chop($stripone) eq '.') {
$one=$stripone;
}
$striptwo=$two;
if (chop($striptwo) eq '.') {
$two=$striptwo;
}
$one =~ tr/A-Z/a-z/;
$two =~ tr/A-Z/a-z/;
return ($one eq $two);
}
sub matchaddrlist {
local($match)=pack('C4', reverse(split(/\./,$_[0],4)));
local($found)=0;
foreach $i (@addrs) {
$found=1 if ($i eq $match);
}
return $found;
}
# there's a better way to do this, it just hasn't evolved from
# my brain to this program yet.
sub byhostname {
@c = reverse(split(/\./,$a));
@d = reverse(split(/\./,$b));
for ($i=0;$i<=(($#c > $#d) ? $#c : $#d) ;$i++) {
next if $c[$i] eq $d[$i];
return -1 if $c[$i] eq "";
return 1 if $d[$i] eq "";
if ($c[$i] eq int($c[$i])) {
# numeric
return $c[$i] <=> $d[$i];
}
else {
# string
return $c[$i] cmp $d[$i];
}
}
return 0;
}
sub checklamer {
local ($isauth)=0;
local ($error)=0;
# must check twice, since first query may be authoritative
# trap stderr here and print if non-empty
open(DIG,"dig soa +noaa $_[0] \@$_[1] 2>&1 1>/dev/null |");
while (<DIG>) {
print " $_[0] NS $_[1]: nameserver error (lame?):\n" if !$error;
print;
$error=1;
}
close(DIG);
return if $error;
open(DIG,"dig soa +noaa $_[0] \@$_[1] 2>/dev/null|");
while (<DIG>) {
if (/status: NXDOMAIN/) {
$isauth=0;
last;
}
if (/status: SERVFAIL/) {
$isauth=0;
last;
}
if (/;; flags.*aa.*;/) {
$isauth=1;
}
}
if (!$isauth) {
print " $_[0] NS $_[1]: lame NS delegation\n";
}
close(DIG);
return;
}